const PENDING = "pending";
const FUlFILLED = "fulfilled";
const REJECT = "reject";

/**
 * 同一对象多次调用then
 * 链式调用
 * return promise
 * return自身时将会出现无限回调 应该抛出报错
 * 捕获执行器中的错误
 * 捕获回调中的错误
 * 适配then中没有写回调的情况
 * 
 * 私有方法 resolve reject finally
 * 静态方法 resolve reject all race 
 */
class MyPromise {
    status = PENDING;
    value = undefined;
    err = undefined;
    successCallback = [];
    errCallback = [];

    constructor(fn) {
        try {
            fn(this.resolve, this.reject);
        } catch (error) {
            this.reject(error);
            throw error;
        }
    }
    // 使用箭头函数将this指向当前实例
    resolve = val => {
        if (this.status !== PENDING) return;

        this.status = FUlFILLED;

        this.value = val;

        while (this.successCallback.length) {
            this.successCallback.shift()();
        }
    };

    reject = err => {
        if (this.status !== PENDING) return;

        this.status = REJECT;

        this.err = err;

        while (this.errCallback.length) {
            this.errCallback.shift()();
        }
    };

    then(successCallback, errCallback) {
        successCallback = successCallback ? successCallback : v => v
        errCallback = errCallback ? errCallback : v => v
        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status === FUlFILLED) {
                setTimeout(() => {
                    try {
                        let x = successCallback(this.value);
                        isPromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                });
            } else if (this.status === REJECT) {
                setTimeout(() => {
                    try {
                        let x = errCallback(this.err);
                        isPromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                });
            } else {
                this.successCallback.push(() => {
                    setTimeout(() => {
                        try {
                            let x = successCallback(this.value);
                            isPromise(promise2, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    });
                });
                this.errCallback.push(() => {
                    setTimeout(() => {
                        try {
                            let x = errCallback(this.err);
                            isPromise(promise2, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    });
                });
            }
        });
        // 链式调用需要返回一个promise 并将上一个then的结果作为参数
        return promise2;
    }

    catch(errCallback) {
        this.then(undefined, errCallback)
    }

    finally (callback) {
        return this.then(value => {
            return MyPromise.resolve(callback()).then(() => value)
        }, err => {
            return MyPromise.resolve(callback()).then(() => {throw err})
        })
      }

    static all(array) {
        let result = []
        let index = 0
        return new MyPromise((resolve, reject) => {
            for (let i = 0; i < array.length; i++) {
                let item = array[i]
                if (item instanceof MyPromise) {
                    item.then(val => addData(i, val), err => reject(err));
                } else {
                    addData(i, item)
                }
            }
    
            function addData(key, val) {
                result[key] = val
                index ++
                if (index === array.length) {
                    resolve(result)
                }
            }
        })
    }

    static race(array) {
        return new MyPromise((resolve, reject) => {
            for (let i = 0; i < array.length; i++) {
                let item = array[i]
                if (item instanceof MyPromise) {
                    item.then(val => addData(i, val), err => reject(err));
                } else {
                    addData(i, item)
                }
            }
    
            function addData(key, val) {
                resolve(val)
                return
            }
        })
    }

    static resolve(val) {
        if (val instanceof MyPromise) return val
        return new MyPromise(resolve => resolve(val))
    }

    static reject(val) {
        if (val instanceof MyPromise) return val
        return new MyPromise((resolve, reject) => reject(val))
    }
}

function isPromise(o, x, resolve, reject) {
    if (x === o) {
        reject(new TypeError("Chaining cycle detected for promise #<Promise>"));
        throw new TypeError("Chaining cycle detected for promise #<Promise>");
        return;
    }
    if (x instanceof MyPromise) {
        x.then(resolve, reject);
    } else {
        resolve(x);
    }
}

module.exports = MyPromise;
